/**
 * @author Jerome Monceaux
 * @version 1.0
 * 
 * All callable object have to implement this class
 **/

package org.hh.common;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;

import android.util.Log;

/**
 * 
 * @author Jerome Monceaux
 *
 * HHObject allow developer to define a class that could be instantiate 
 * and linked to other from a configuration file or code. 
 */
public abstract class HHObject extends Observable implements Observer {
	
	public String fName;

	ArrayList<Object[]> fWires = new ArrayList<Object[]>();
	HashMap<Object[], Method> fWireMethod = new HashMap<Object[], Method>();
	
	public HHObject(String fName) {
		super();
		this.fName = fName;
	}
	
	public void setOutput(String name, Object[] o){
		Object[] v = new Object[2];
		v[0] = name;
		v[1] = o;
		setChanged();
		notifyObservers(v);
	}
	
	
	public void setWire(Observable o, String shortOutputName, String shortInputName){
		for( Object[] w : fWires ){
			if( w[0] == shortInputName && w[1] == shortOutputName ) return;
		}
		Object[] w = new Object[2];
		w[0] = shortInputName;
		w[1] = shortOutputName;
		fWires.add(w);
		o.addObserver(this);
		//o.notifyObservers();
	}
	public void removeAllWireFromInput(Observable o, String shortInputName){
		ArrayList<Object[]> toDelete = new ArrayList<Object[]>();
		for( Object[] w : fWires ){
			if( w[0] == shortInputName )
				toDelete.add(w);
		}
		for( Object[] d : toDelete ){
			fWires.remove(d);
			fWireMethod.remove(d);
		}
		
		//o.deleteObserver(this); // TODO : only if there is no wire anymore
		o.notifyObservers();
	}
	public void removeWire(Observable o, String shortOutputName, String shortInputName){
		ArrayList<Object[]> toDelete = new ArrayList<Object[]>();
		for( Object[] w : fWires ){
			if( w[0] == shortInputName && w[1] == shortOutputName )
				toDelete.add(w);
		}
		for( Object[] d : toDelete ){
			fWires.remove(d);
			fWireMethod.remove(d);
		}
		
		// o.deleteObserver(this); // TODO : only if there is no wire anymore
		o.notifyObservers();
	}
	
	@Override
	public void update(Observable o, Object arg){
		String outputName = (String) ((Object[])arg)[0];

		// explore map to find all output
		for( Object[] w : fWires ){
			if( w[1].equals(outputName) )
					setInput(w, (String)w[0], ((Object[])((Object[])arg)[1]));
		}
	}
	
	// setInput signature mainly used by xmlrpc server
	public Object[] setInput(String inputName, ArrayList<Object> params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		Object[] p = new Object[params.size()];
		for(int i = 0; i < params.size(); i++)
		{
			p[i] = params.get(i);
		}
		Object r = setInput(null, inputName, p);
		if (r == null) return null; 
		return new Object[]{r};
	}
	
	public Object setInput(Object[]wire, String inputName, Object[] args){
		Method mQuick = fWireMethod.get(wire); //great CPU optimization
		if( mQuick != null){
			try {
				return mQuick.invoke(this,args);
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			fWireMethod.remove(wire);
		}
		
		// First call is used to fill the fWireMethod map.
		// So, this will not be necessary next time for this wire.
		@SuppressWarnings("rawtypes")
		Class[] paramTypes = null;
		if(args != null)
		{
			paramTypes = new Class[args.length];
		  for(int i=0;i<args.length;++i)
		  {
		  	paramTypes[i] = args[i].getClass();
		  }
		}
		try {
			Method m;
			@SuppressWarnings("rawtypes")
			Class c = this.getClass();
			m = ReflectionUtil.getCompatibleMethod(c, inputName, paramTypes);
			fWireMethod.put(wire, m);
			return m.invoke(this,args);
		} catch (SecurityException e) {
			Log.e(fName, e.toString());
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			Log.e(fName, e.toString());
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			Log.e(fName, e.toString());
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			Log.e(fName, e.toString());
			e.printStackTrace();
		}
		fWireMethod.remove(wire);
		return null;
	}

}
